package com.gyj.server.core;

import com.gyj.server.entity.Applications;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

/**
 * 服务注册表缓存
 * @author Gao
 * @date 2021/7/2
 */
public class ServiceRegistryCache {

    private static final Logger logger = LogManager.getLogger(ServiceRegistryCache.class);

    /**
     * 服务注册表缓存的单例
     */
    private static final ServiceRegistryCache instance = new ServiceRegistryCache();

    /**
     * 获取服务注册表缓存的单例
     * @return
     */
    public static ServiceRegistryCache getInstance() {
        return instance;
    }

    /**
     * 缓存数据同步间隔
     */
    private static final Long CACHE_MAP_SYNC_INTERVAL = 30 * 1000L;

    /**
     * 缓存key
     */
    public static class CacheKey {
        /**
         * 全量注册表缓存key
         */
        public static final String FULL_SERVICE_REGISTRY = "full_service_registry";
        /**
         * 增量注册表缓存key
         */
        public static final String DELTA_SERVICE_REGISTRY = "delta_service_registry";
    }

    /**
     * 注册表数据
     */
    private ServiceRegistry registry = ServiceRegistry.getInstance();

    /**
     * 只读缓存
     */
    private Map<String, Object> readOnlyMap = new HashMap<>();
    /**
     * 读写缓存
     */
    private Map<String, Object> readWriteMap = new HashMap<>();
    /**
     * cache map同步后台线程
     */
    private CacheMapSyncDaemon cacheMapSyncDaemon;
    /**
     * 内部锁，不同的功能使用的不同的锁，避免锁冲突，也能保证数据的准确性
     */
    private Object lock = new Object();
    /**
     * 对readOnlyMap的读写锁
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private ReadLock readLock = readWriteLock.readLock();
    private WriteLock writeLock = readWriteLock.writeLock();

    /**
     * 构造函数
     */
    public ServiceRegistryCache () {
        // 启动缓存数据同步的后台线程
        this.cacheMapSyncDaemon = new CacheMapSyncDaemon();
        this.cacheMapSyncDaemon.setDaemon(true);
        this.cacheMapSyncDaemon.start();
    }

    /**
     * 根据缓存key获取数据
     * 大幅度的降低了高频繁的读操作对服务注册表加读锁的请求，
     * 避免了频繁的服务注册表的读锁和写锁的竞争的冲突
     * 系统刚启动的时候，会有一个线程来填充各级缓存的数据
     * 此后的30秒内，大家全部都是读缓存数据的，不会涉及到任何加锁的行为
     *
     * @param cacheKey
     * @return
     */
    public Object get(String cacheKey) {
        Object cacheValue = null;
        try {
            // 没有这一层的读锁会怎样，直接从缓存中读数据，大量的读锁可以并发读数据
            readLock.lock();
            cacheValue = readOnlyMap.get(cacheKey);
            if (null == cacheValue) {
                ///logger.info("工作线程获取了读锁，尝试获取lock锁。。。");
                //加锁填充数据, 先读readWriteMap，有数据则填充到readOnlyMap
                synchronized (lock) {
                    if (null == readOnlyMap.get(cacheKey)) {
                        cacheValue = readWriteMap.get(cacheKey);
                        if (null == cacheValue) {
                            // 最后直接拉取内存中的注册表, 填充两个缓存Map
                            cacheValue = getCacheValue(cacheKey);
                            logger.info("根据缓存key获取数据, 两个map缓存数据不存在，" +
                                    "直接从内存注册表拉取返回cacheValue:{}", cacheValue);
                            readWriteMap.put(cacheKey, cacheValue);
                        }
                        readOnlyMap.put(cacheKey, cacheValue);
                    }
                }
            }
        } finally {
            readLock.unlock();
        }
        return cacheValue;
    }

    /**
     * 获取实际的缓存数据
     * @param cacheKey
     * @return
     */
    public Object getCacheValue(String cacheKey) {
        try {
            readLock.lock();
            // 获取全量注册表
            if (CacheKey.FULL_SERVICE_REGISTRY.equals(cacheKey)) {
                return new Applications(registry.getRegistry());
            }
            // 获取增量注册表
            else if (CacheKey.DELTA_SERVICE_REGISTRY.equals(cacheKey)) {
                return registry.getDeltaRegistry();
            }
        } finally {
            readLock.unlock();
        }
        return null;
    }

    /**
     * 过期全量和增量对应的缓存
     */
    public void invalidate() {
        synchronized (lock) {
            readWriteMap.remove(CacheKey.FULL_SERVICE_REGISTRY);
            readWriteMap.remove(CacheKey.DELTA_SERVICE_REGISTRY);
        }
    }

    // 第二层不加锁会怎样
   /* synchronized (lock) {
        // 全量数据，保持两个map数据一致
        if (readWriteMap.get(CacheKey.FULL_SERVICE_REGISTRY) == null) {
            readOnlyMap.put(CacheKey.FULL_SERVICE_REGISTRY, null);
        }
        // 增量数据
        if (readWriteMap.get(CacheKey.DELTA_SERVICE_REGISTRY) == null) {
            readOnlyMap.put(CacheKey.DELTA_SERVICE_REGISTRY, null);
        }
    }*/

    /**
     * 两个map缓存同步的线程
     * 每30s缓存同步的时候才有可能跟上面的读缓存冲突，读写互斥，但是频率是很低的
     *
     * 在写数据的期间，读数据不涉及任何读写锁的冲突，直接读的cache数据
     * 有一个后台的线程，可能会过30秒之后，对缓存加个锁，然后同步两个缓存的数据，在这个过程中，
     * 实际上来说也是不会对高频的读操作施加任何的影响的。
     * 只有在此时，会有线程感知到缓存数据是null，重新填充数据，重新填充数据的时候，
     * 会涉及到重新从服务注册表查数据，然后加读锁，此时就是一个线程加了一个读锁，而且是很快的行为，
     * 大幅的降低了频繁的读操作，可能频繁的跟写操作，读写锁冲突的问题，
     * 大幅度的降低了读写锁的互斥冲突的问题。避免了说高频的读操作，对服务注册表频繁的读取，
     * 频繁的加读锁，导致跟服务注册表的写锁频繁的互斥和冲突。
     * 各种锁的运用，加了两级缓存，尽可能的把读写锁的互斥和冲突，降低到了最低
     */
    private class CacheMapSyncDaemon extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    try {
                        // 死锁优化，调整加锁的顺序，两个线程有一个获取写锁，就不能同时获取读锁了，互斥操作
                        writeLock.lock();
                        synchronized (lock) {
                            ///logger.info("CacheMapSyncDaemon线程获取了lock锁，准备获取写锁，休眠10s");
                            ///Thread.sleep(10 * 1000);
                            // 全量数据，保持两个map数据一致
                            if (readWriteMap.get(CacheKey.FULL_SERVICE_REGISTRY) == null) {
                                readOnlyMap.put(CacheKey.FULL_SERVICE_REGISTRY, null);
                            }
                            // 增量数据
                            if (readWriteMap.get(CacheKey.DELTA_SERVICE_REGISTRY) == null) {
                                readOnlyMap.put(CacheKey.DELTA_SERVICE_REGISTRY, null);
                            }
                        }
                    } finally {
                        writeLock.unlock();
                    }
                    Thread.sleep(CACHE_MAP_SYNC_INTERVAL);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 下面是eureka源码，依次遍历，如果不一致以readWriteCacheMap为准，
    // 覆盖onlyMap中的缓存, 用于同步缓存数据
 /*   private TimerTask getCacheUpdateTask() {
        return new TimerTask() {
            @Override
            public void run() {
                logger.debug("Updating the client cache from response cache");
                // 循环readOnlyCacheMap里面的KEY
                for (Key key : readOnlyCacheMap.keySet()) {
                    if (logger.isDebugEnabled()) {
                        Object[] args = {key.getEntityType(), key.getName(),
                                key.getVersion(), key.getType()};
                        logger.debug("Updating the client cache from response cache
                        for key : {} {} {} {}", args);
                    }
                    try {
                        // 版本号
                        CurrentRequestVersion.set(key.getVersion());
                        // 从readWriteCacheMap获取数据
                        Value cacheValue = readWriteCacheMap.get(key);
                        // 当前的只读数据
                        Value currentCacheValue = readOnlyCacheMap.get(key);
                        // 判断数据是否一致
                        if (cacheValue != currentCacheValue) {
                            // 如果不一致，覆盖只读缓存里面的数据，以readWriteCacheMap为准
                            readOnlyCacheMap.put(key, cacheValue);
                        }
                    } catch (Throwable th) {
                        logger.error("Error while updating the client cache from response cache", th);
                    }
                }
            }
        };
    }*/

    // 源码中的获取缓存
    /*String get(final Key key, boolean useReadOnlyCache) {
        // 主要看这个getValue
        Value payload = getValue(key, useReadOnlyCache);
        if (payload == null || payload.getPayload().equals(EMPTY_PAYLOAD)) {
            return null;
        } else {
            return payload.getPayload();
        }
    }
    Value getValue(final Key key, boolean useReadOnlyCache) {
        Value payload = null;
        try {
            // 是否使用只读缓存
            if (useReadOnlyCache) {
                // 从只读缓存里面获取数据
                final Value currentPayload = readOnlyCacheMap.get(key);
                if (currentPayload != null) {
                    // 不为空的话，直接返回数据
                    payload = currentPayload;
                } else {
                    // 只读缓存里面没有，就到读写缓存里面去获取。
                    payload = readWriteCacheMap.get(key);
                    // 同时将数据，放入只读缓存。
                    readOnlyCacheMap.put(key, payload);
                }
            } else {
                // 不使用只读缓存
                payload = readWriteCacheMap.get(key);
            }
        } catch (Throwable t) {
            logger.error("Cannot get value for key :" + key, t);
        }
        return payload;
    }*/

}
